package com.fau.wmbj.server.repository.base;

import cn.hutool.core.collection.CollUtil;
import com.fau.wmbj.server.entity.BaseEntity;
import com.fau.wmbj.server.entity.OrderedEntity;
import com.fau.wmbj.server.utils.Filter;
import com.fau.wmbj.server.utils.Page;
import com.fau.wmbj.server.utils.Pageable;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.util.Assert;

import javax.persistence.*;
import javax.persistence.criteria.*;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.*;

import static com.fau.wmbj.server.utils.Filter.Operator.or;

public class BaseRepositoryImpl<T extends BaseEntity<ID>, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {

    /**
     * 属性分隔符
     */
    private static final String ATTRIBUTE_SEPARATOR = ".";

    /**
     * 别名前缀
     */
    private static final String ALIAS_PREFIX = "fauGeneratedAlias";

    /**
     * 别名数
     */
    private static volatile long aliasCount = 0L;

    protected EntityManager entityManager;
    /**
     * 实体类类型
     */
    protected Class<T> entityClass;

    /**
     * 构造方法
     */
    @Autowired(required = false)
    public BaseRepositoryImpl(JpaEntityInformation<T, ID> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.entityManager = entityManager;
        this.entityClass = entityInformation.getJavaType();
    }

    @Override
    public String max(List<Filter> filters, String max) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<T> root = criteriaQuery.from(entityClass);

        Predicate restrictions = criteriaQuery.getRestriction() != null ? criteriaQuery.getRestriction() : criteriaBuilder.conjunction();
        restrictions = criteriaBuilder.and(restrictions, toPredicate(root, criteriaQuery, filters));
        criteriaQuery.where(restrictions);
        CriteriaQuery<Object> sumCriteriaQuery = criteriaBuilder.createQuery(Object.class);
        copyCriteriaWithoutSelectionAndOrder(criteriaQuery, sumCriteriaQuery);
        Path objectPath = root.get(max);
        sumCriteriaQuery.select(criteriaBuilder.max(objectPath));
        Object singleResult = entityManager.createQuery(sumCriteriaQuery).getSingleResult();
        return singleResult != null ? singleResult.toString() : null;
    }

    @Override
    public String min(List<Filter> filters, String min) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<T> root = criteriaQuery.from(entityClass);

        Predicate restrictions = criteriaQuery.getRestriction() != null ? criteriaQuery.getRestriction() : criteriaBuilder.conjunction();
        restrictions = criteriaBuilder.and(restrictions, toPredicate(root, criteriaQuery, filters));
        criteriaQuery.where(restrictions);
        CriteriaQuery<String> sumCriteriaQuery = criteriaBuilder.createQuery(String.class);
        copyCriteriaWithoutSelectionAndOrder(criteriaQuery, sumCriteriaQuery);
        Path objectPath = root.get(min);
        sumCriteriaQuery.select(criteriaBuilder.min(objectPath));
        String singleResult = entityManager.createQuery(sumCriteriaQuery).getSingleResult();
        return singleResult.toString();
    }

    @Override
    public BigDecimal sum(List<Filter> filters, String sum) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<T> root = criteriaQuery.from(entityClass);

        Predicate restrictions = criteriaQuery.getRestriction() != null ? criteriaQuery.getRestriction() : criteriaBuilder.conjunction();
        restrictions = criteriaBuilder.and(restrictions, toPredicate(root, criteriaQuery, filters));
        criteriaQuery.where(restrictions);
        CriteriaQuery<BigDecimal> sumCriteriaQuery = criteriaBuilder.createQuery(BigDecimal.class);
        copyCriteriaWithoutSelectionAndOrder(criteriaQuery, sumCriteriaQuery);
        Path objectPath = root.get(sum);
        sumCriteriaQuery.select(criteriaBuilder.sum(objectPath));
        List<BigDecimal> resultList = entityManager.createQuery(sumCriteriaQuery).setMaxResults(1).getResultList();
        return CollUtil.isNotEmpty(resultList) ? resultList.get(0) : null;
    }

    @Override
    public boolean exists(String attributeName, Object attributeValue) {
        Assert.hasText(attributeName, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
        Root<T> root = criteriaQuery.from(entityClass);
        criteriaQuery.select(criteriaBuilder.count(root));
        criteriaQuery.where(criteriaBuilder.equal(root.get(attributeName), attributeValue));
        TypedQuery<Long> query = entityManager.createQuery(criteriaQuery);
        Long singleResult = query.getSingleResult();
        return singleResult > 0;
    }

    @Override
    public boolean exists(String attributeName, String attributeValue, boolean ignoreCase) {
        Assert.hasText(attributeName, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
        Root<T> root = criteriaQuery.from(entityClass);
        criteriaQuery.select(criteriaBuilder.count(root));
        if (ignoreCase) {
            criteriaQuery.where(criteriaBuilder.equal(criteriaBuilder.lower(root.<String>get(attributeName)), StringUtils.lowerCase(attributeValue)));
        } else {
            criteriaQuery.where(criteriaBuilder.equal(root.get(attributeName), attributeValue));
        }
        TypedQuery<Long> query = entityManager.createQuery(criteriaQuery);
        return query.getSingleResult() > 0;
    }

    @Override
    public boolean unique(ID id, String attributeName, Object attributeValue) {
        Assert.hasText(attributeName, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");

        if (id != null) {
            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
            CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
            Root<T> root = criteriaQuery.from(entityClass);
            criteriaQuery.select(criteriaBuilder.count(root));
            criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(root.get(attributeName), attributeValue), criteriaBuilder.notEqual(root.get(BaseEntity.ID_PROPERTY_NAME), id)));
            TypedQuery<Long> query = entityManager.createQuery(criteriaQuery);
            return query.getSingleResult() <= 0;
        } else {
            return !exists(attributeName, attributeValue);
        }
    }

    @Override
    public boolean unique(ID id, String attributeName, String attributeValue, boolean ignoreCase) {
        Assert.hasText(attributeName, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");

        if (id != null) {
            CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
            CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
            Root<T> root = criteriaQuery.from(entityClass);
            criteriaQuery.select(criteriaBuilder.count(root));
            if (ignoreCase) {
                criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(criteriaBuilder.lower(root.<String>get(attributeName)), StringUtils.lowerCase(attributeValue)), criteriaBuilder.notEqual(root.get(BaseEntity.ID_PROPERTY_NAME), id)));
            } else {
                criteriaQuery.where(criteriaBuilder.and(criteriaBuilder.equal(root.get(attributeName), attributeValue), criteriaBuilder.notEqual(root.get(BaseEntity.ID_PROPERTY_NAME), id)));
            }
            TypedQuery<Long> query = entityManager.createQuery(criteriaQuery);
            return query.getSingleResult() <= 0;
        } else {
            return !exists(attributeName, attributeValue);
        }
    }

    @Override
    public T find(ID id) {
        if (id == null) {
            return null;
        }
        return entityManager.find(entityClass, id);
    }

    @Override
    public T find(ID id, LockModeType lockModeType) {
        if (id == null) {
            return null;
        }
        if (lockModeType != null) {
            return entityManager.find(entityClass, id, lockModeType);
        } else {
            return entityManager.find(entityClass, id);
        }
    }

    @Override
    public T findByJPQL(String jpql, Map<String, Object> params, LockModeType lockModeType) {
        TypedQuery<T> tTypedQuery = entityManager.createQuery(jpql, entityClass).setLockMode(lockModeType);
        if (params != null) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                tTypedQuery.setParameter(entry.getKey(), entry.getValue());
            }
        }
        return tTypedQuery.getSingleResult();
    }

    @Override
    public T find(String attributeName, Object attributeValue) {
        Assert.hasText(attributeName, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");

        return find(attributeName, attributeValue, null);
    }

    @Override
    public T find(String attributeName, String attributeValue, boolean ignoreCase) {
        Assert.hasText(attributeName, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");

        return find(attributeName, attributeValue, ignoreCase, null);
    }

    @Override
    public T find(String attributeName, Object attributeValue, LockModeType lockModeType) {
        Assert.hasText(attributeName, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<T> root = criteriaQuery.from(entityClass);
        criteriaQuery.select(root);
        criteriaQuery.where(criteriaBuilder.equal(root.get(attributeName), attributeValue));
        TypedQuery<T> query = entityManager.createQuery(criteriaQuery);
        if (lockModeType != null) {
            query.setLockMode(lockModeType);
        }
        try {
            return query.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    @Override
    public T find(String attributeName, String attributeValue, boolean ignoreCase, LockModeType lockModeType) {
        Assert.hasText(attributeName, "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank");

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        Root<T> root = criteriaQuery.from(entityClass);
        criteriaQuery.select(root);
        criteriaQuery.where();
        if (ignoreCase) {
            criteriaQuery.where(criteriaBuilder.equal(criteriaBuilder.lower(root.<String>get(attributeName)), StringUtils.lowerCase(attributeValue)));
        } else {
            criteriaQuery.where(criteriaBuilder.equal(root.get(attributeName), attributeValue));
        }
        TypedQuery<T> query = entityManager.createQuery(criteriaQuery);
        if (lockModeType != null) {
            query.setLockMode(lockModeType);
        }
        try {
            return query.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    @Override
    public List<T> findList(Integer first, Integer count, List<Filter> filters, List<com.fau.wmbj.server.utils.Order> orders) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        criteriaQuery.select(criteriaQuery.from(entityClass));
        return findList(criteriaQuery, first, count, filters, orders);
    }

    @Override
    public Page<T> findPage(Pageable pageable) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        criteriaQuery.select(criteriaQuery.from(entityClass));
        return findPage(criteriaQuery, pageable);
    }

    @Override
    public long count(Filter... filters) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        criteriaQuery.select(criteriaQuery.from(entityClass));
        return count(criteriaQuery, ArrayUtils.isNotEmpty(filters) ? Arrays.asList(filters) : null);
    }

    @Override
    public long count(List<Filter> filters) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(entityClass);
        criteriaQuery.select(criteriaQuery.from(entityClass));
        return count(criteriaQuery, filters);
    }

    @Override
    @Modifying
    public void persist(T entity) {
        Assert.notNull(entity, "[Assertion failed] - entity is required; it must not be null");

        entityManager.persist(entity);
    }

    @Override
    public T merge(T entity) {
        Assert.notNull(entity, "[Assertion failed] - entity is required; it must not be null");

        return entityManager.merge(entity);
    }

    @Override
    public void remove(T entity) {
        if (entity != null) {
            T merge = entityManager.merge(entity);
            entityManager.remove(merge);
            entityManager.flush();
        }
    }

    @Override
    public void refresh(T entity) {
        if (entity != null) {
            entityManager.refresh(entity);
        }
    }

    @Override
    public void refresh(T entity, LockModeType lockModeType) {
        if (entity != null) {
            if (lockModeType != null) {
                entityManager.refresh(entity, lockModeType);
            } else {
                entityManager.refresh(entity);
            }
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public ID getIdentifier(T entity) {
        Assert.notNull(entity, "entity is null");

        return (ID) entityManager.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(entity);
    }

    @Override
    public boolean isLoaded(T entity) {
        Assert.notNull(entity, "entity is null");

        return entityManager.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity);
    }

    @Override
    public boolean isLoaded(T entity, String attributeName) {
        Assert.notNull(entity, "entity is null");
        Assert.hasText(attributeName, "attributeName is empty");

        return entityManager.getEntityManagerFactory().getPersistenceUnitUtil().isLoaded(entity, attributeName);
    }

    @Override
    public boolean isManaged(T entity) {
        Assert.notNull(entity, "entity is null");

        return entityManager.contains(entity);
    }

    @Override
    public void detach(T entity) {
        if (entity != null) {
            entityManager.detach(entity);
        }
    }

    @Override
    public LockModeType getLockMode(T entity) {
        Assert.notNull(entity, "entity is null");

        return entityManager.getLockMode(entity);
    }

    @Override
    public void lock(T entity, LockModeType lockModeType) {
        if (entity != null && lockModeType != null) {
            entityManager.lock(entity, lockModeType);
        }
    }

    @Override
    public void clear() {
        entityManager.clear();
    }

    /**
     * 查找实体对象集合
     *
     * @param criteriaQuery 查询条件
     * @param first         起始记录
     * @param count         数量
     * @param filters       筛选
     * @param orders        排序
     * @param lockModeType  锁定方式
     * @return 实体对象集合
     */
    protected List<T> findList(CriteriaQuery<T> criteriaQuery, Integer first, Integer count, List<Filter> filters, List<com.fau.wmbj.server.utils.Order> orders, LockModeType lockModeType) {
        Assert.notNull(criteriaQuery, "[Assertion failed] - criteriaQuery is required; it must not be null");
        Assert.notNull(criteriaQuery.getSelection(), "[Assertion failed] - criteriaQuery selection is required; it must not be null");
        Assert.notEmpty(criteriaQuery.getRoots(), "[Assertion failed] - criteriaQuery roots must not be empty: it must contain at least 1 root");

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        Root<T> root = findRoot(criteriaQuery, criteriaQuery.getResultType());

        Predicate restrictions = criteriaQuery.getRestriction() != null ? criteriaQuery.getRestriction() : criteriaBuilder.conjunction();
        restrictions = criteriaBuilder.and(restrictions, toPredicate(root, criteriaQuery, filters));
        criteriaQuery.where(restrictions);

        List<Order> orderList = new ArrayList<>();
        orderList.addAll(criteriaQuery.getOrderList());
        orderList.addAll(toOrders(root, orders));
        if (CollUtil.isEmpty(orderList)) {
            if (OrderedEntity.class.isAssignableFrom(entityClass)) {
                orderList.add(criteriaBuilder.asc(getPath(root, OrderedEntity.ORDER_PROPERTY_NAME)));
            } else {
                orderList.add(criteriaBuilder.desc(getPath(root, OrderedEntity.CREATED_DATE_PROPERTY_NAME)));
            }
        }
        criteriaQuery.orderBy(orderList);

        TypedQuery<T> query = entityManager.createQuery(criteriaQuery);
        if (first != null) {
            query.setFirstResult(first);
        }
        if (count != null) {
            query.setMaxResults(count);
        }
        if (lockModeType != null) {
            query.setLockMode(lockModeType);
        }
        return query.getResultList();
    }

    /**
     * 查找实体对象集合
     *
     * @param criteriaQuery 查询条件
     * @param first         起始记录
     * @param count         数量
     * @param filters       筛选
     * @param orders        排序
     * @return 实体对象集合
     */
    protected List<T> findList(CriteriaQuery<T> criteriaQuery, Integer first, Integer count, List<Filter> filters, List<com.fau.wmbj.server.utils.Order> orders) {
        return findList(criteriaQuery, first, count, filters, orders, null);
    }

    /**
     * 查找实体对象集合
     *
     * @param criteriaQuery 查询条件
     * @param first         起始记录
     * @param count         数量
     * @return 实体对象集合
     */
    protected List<T> findList(CriteriaQuery<T> criteriaQuery, Integer first, Integer count) {
        return findList(criteriaQuery, first, count, null, null, null);
    }

    /**
     * 查找实体对象集合
     *
     * @param criteriaQuery 查询条件
     * @return 实体对象集合
     */
    protected List<T> findList(CriteriaQuery<T> criteriaQuery) {
        return findList(criteriaQuery, null, null, null, null, null);
    }

    /**
     * 查找实体对象分页
     *
     * @param criteriaQuery 查询条件
     * @param pageable      分页信息
     * @param lockModeType  锁定方式
     * @return 实体对象分页
     */
    protected Page<T> findPage(CriteriaQuery<T> criteriaQuery, Pageable pageable, LockModeType lockModeType) {
        Assert.notNull(criteriaQuery, "[Assertion failed] - criteriaQuery is required; it must not be null");
        Assert.notNull(criteriaQuery.getSelection(), "[Assertion failed] - criteriaQuery selection is required; it must not be null");
        Assert.notEmpty(criteriaQuery.getRoots(), "[Assertion failed] - criteriaQuery roots must not be empty: it must contain at least 1 root");

        if (pageable == null) {
            pageable = new Pageable();
        }

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        Root<T> root = findRoot(criteriaQuery, criteriaQuery.getResultType());

        Predicate restrictions = criteriaQuery.getRestriction() != null ? criteriaQuery.getRestriction() : criteriaBuilder.conjunction();
        restrictions = criteriaBuilder.and(restrictions, toPredicate(root, criteriaQuery, pageable.getFilters()));
        String searchProperty = pageable.getSearchProperty();
        String searchValue = pageable.getSearchValue();

        //构造搜索条件
        if (StringUtils.isNotEmpty(searchValue)) {
            if (StringUtils.isNotEmpty(searchProperty)) {
                if (StringUtils.contains(searchProperty, ",")) {
                    String[] searchPropertyArr = searchProperty.split(",");
                    if (searchPropertyArr.length > 0) {
                        Predicate[] predicates = new Predicate[searchPropertyArr.length];
                        for (int i = 0; i < searchPropertyArr.length; i++) {
                            Path<String> searchPath = getPath(root, searchPropertyArr[i]);
                            if (searchPath != null) {
                                predicates[i] = criteriaBuilder.like(searchPath, "%" + searchValue + "%");
                            }
                        }
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.or(predicates));
                    }

                } else {
                    Path<String> searchPath = getPath(root, searchProperty);
                    if (searchPath != null) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(searchPath, "%" + searchValue + "%"));
                    }
                }

            } else {
                Class<T> aClass = getDomainClass();
                Field[] classFields = aClass.getDeclaredFields();
                List<String> fieldNameList = new ArrayList<>();
                for (Field field : classFields) {
                    Class<?> type = field.getType();
                    Annotation[] annotations = field.getAnnotations();
                    boolean isDbColumn = false;
                    for (Annotation annotation : annotations) {
                        Class<? extends Annotation> annotationType = annotation.annotationType();
                        String name = annotationType.getName();
                        if (StringUtils.contains(name, "Column")) {
                            isDbColumn = true;
                        }
                    }
                    if (type == String.class && isDbColumn) {
                        String fieldName = field.getName();
                        fieldNameList.add(fieldName);
                    }
                }
                Predicate[] predicates = new Predicate[fieldNameList.size()];
                for (int i = 0; i < fieldNameList.size(); i++) {
                    // List<Filter> filters = new ArrayList<>();
                    // filters.add(Filter.like(fieldNameList.get(i), "%" + searchValue + "%"));
                    // predicates[i] = toPredicate(root, criteriaQuery, filters);
                    Path<String> searchPath = getPath(root, fieldNameList.get(i));
                    if (searchPath != null) {
                        predicates[i] = criteriaBuilder.like(searchPath, "%" + searchValue + "%");
                    }
                }
                restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.or(predicates));
            }
        }
        criteriaQuery.where(restrictions);

        List<Order> orderList = new ArrayList<>();
        orderList.addAll(criteriaQuery.getOrderList());
        orderList.addAll(toOrders(root, pageable.getOrders()));
        String orderProperty = pageable.getOrderProperty();
        com.fau.wmbj.server.utils.Order.Direction orderDirection = pageable.getOrderDirection();
        if (StringUtils.isNotEmpty(orderProperty) && orderDirection != null) {
            Path<?> orderPath = getPath(root, orderProperty);
            if (orderPath != null) {
                switch (orderDirection) {
                    case asc:
                        orderList.add(criteriaBuilder.asc(orderPath));
                        break;
                    case desc:
                        orderList.add(criteriaBuilder.desc(orderPath));
                        break;
                    case str_asc:
                        orderList.add(criteriaBuilder.asc(root.get(orderProperty).as(Integer.class)));
                        break;
                    case str_desc:
                        orderList.add(criteriaBuilder.desc(root.get(orderProperty).as(Integer.class)));
                        break;
                    default:
                        break;
                }
            }
        }


        if (CollUtil.isEmpty(orderList) && BaseEntity.class.isAssignableFrom(entityClass)) {
            if (OrderedEntity.class.isAssignableFrom(entityClass)) {
                orderList.add(criteriaBuilder.asc(getPath(root, OrderedEntity.ORDER_PROPERTY_NAME)));
            } else {
                orderList.add(criteriaBuilder.desc(getPath(root, OrderedEntity.LAST_MODIFIED_DATE_PROPERTY_NAME)));
            }
        }
        criteriaQuery.orderBy(orderList);
        long total = count(criteriaQuery, null);
        TypedQuery<T> query = entityManager.createQuery(criteriaQuery.distinct(true));
        query.setFirstResult((pageable.getPageNumber() - 1) * pageable.getPageSize());
        query.setMaxResults(pageable.getPageSize());
        if (lockModeType != null) {
            query.setLockMode(lockModeType);
        }
        return new Page<>(query.getResultList(), total, pageable);
    }

    /**
     * 查找实体对象分页
     *
     * @param criteriaQuery 查询条件
     * @param pageable      分页信息
     * @return 实体对象分页
     */
    protected Page<T> findPage(CriteriaQuery<T> criteriaQuery, Pageable pageable) {
        return findPage(criteriaQuery, pageable, null);
    }

    /**
     * 查询实体对象数量
     *
     * @param criteriaQuery 查询条件
     * @param filters       筛选
     * @return 实体对象数量
     */
    protected Long count(CriteriaQuery<T> criteriaQuery, List<Filter> filters) {
        Assert.notNull(criteriaQuery, "[Assertion failed] - criteriaQuery is required; it must not be null");
        Assert.notNull(criteriaQuery.getSelection(), "[Assertion failed] - criteriaQuery selection is required; it must not be null");
        Assert.notEmpty(criteriaQuery.getRoots(), "[Assertion failed] - criteriaQuery roots must not be empty: it must contain at least 1 root");

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        Root<T> root = findRoot(criteriaQuery, criteriaQuery.getResultType());
        criteriaBuilder.countDistinct(root);
        Predicate restrictions = criteriaQuery.getRestriction() != null ? criteriaQuery.getRestriction() : criteriaBuilder.conjunction();
        restrictions = criteriaBuilder.and(restrictions, toPredicate(root, criteriaQuery, filters));
        criteriaQuery.where(restrictions);

        CriteriaQuery<Long> countCriteriaQuery = criteriaBuilder.createQuery(Long.class);
        copyCriteriaWithoutSelectionAndOrder(criteriaQuery, countCriteriaQuery);

        Root<?> countRoot = findRoot(countCriteriaQuery, criteriaQuery.getResultType());
        if (criteriaQuery.isDistinct()) {
            countCriteriaQuery.select(criteriaBuilder.countDistinct(countRoot));
        } else {
            countCriteriaQuery.select(criteriaBuilder.count(countRoot));
        }
        return entityManager.createQuery(countCriteriaQuery).getSingleResult();
    }

    /**
     * 查询实体对象数量
     *
     * @param criteriaQuery 查询条件
     * @param filters       筛选
     * @return 实体对象数量
     */
    protected Long countAttribute(CriteriaQuery<T> criteriaQuery, List<Filter> filters, String attribute) {
        Assert.notNull(criteriaQuery, "criteriaQuery is null");
        Assert.notNull(criteriaQuery.getSelection(), "criteriaQuery selection is null");
        Assert.notEmpty(criteriaQuery.getRoots(), "criteriaQuery roots is empty");

        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        Root<T> root = findRoot(criteriaQuery, criteriaQuery.getResultType());
        criteriaBuilder.countDistinct(root);
        Predicate restrictions = criteriaQuery.getRestriction() != null ? criteriaQuery.getRestriction() : criteriaBuilder.conjunction();
        restrictions = criteriaBuilder.and(restrictions, toPredicate(root, criteriaQuery, filters));
        criteriaQuery.where(restrictions);

        CriteriaQuery<Long> countCriteriaQuery = criteriaBuilder.createQuery(Long.class);
        copyCriteriaWithoutSelectionAndOrder(criteriaQuery, countCriteriaQuery);

        Root<?> countRoot = findRoot(countCriteriaQuery, criteriaQuery.getResultType());
        countCriteriaQuery.select(criteriaBuilder.countDistinct(countRoot.get(attribute)));
        return entityManager.createQuery(countCriteriaQuery).getSingleResult();
    }

    /**
     * 查询实体对象数量
     *
     * @param criteriaQuery 查询条件
     * @return 实体对象数量
     */
    protected Long count(CriteriaQuery<T> criteriaQuery) {
        return count(criteriaQuery, null);
    }

    /**
     * 查找Root
     *
     * @param criteriaQuery 查询条件
     * @param clazz         类型
     * @return Root
     */
    @SuppressWarnings("unchecked")
    Root<T> findRoot(CriteriaQuery<?> criteriaQuery, Class<T> clazz) {
        Assert.notNull(criteriaQuery, "[Assertion failed] - criteriaQuery is required; it must not be null");
        Assert.notNull(clazz, "[Assertion failed] - clazz is required; it must not be null");

        for (Root<?> root : criteriaQuery.getRoots()) {
            if (clazz.equals(root.getJavaType())) {
                return (Root<T>) root.as(clazz);
            }
        }
        return (Root<T>) criteriaQuery.getRoots().iterator().next();
    }

    /**
     * 获取Path
     *
     * @param path          Path
     * @param attributeName 属性名称
     * @return Path
     */
    @SuppressWarnings("unchecked")
    private <X> Path<X> getPath(Path<?> path, String attributeName) {
        if (path == null || StringUtils.isEmpty(attributeName)) {
            return (Path<X>) path;
        }
        return getPath(path.get(StringUtils.substringBefore(attributeName, ATTRIBUTE_SEPARATOR)), StringUtils.substringAfter(attributeName, ATTRIBUTE_SEPARATOR));
    }

    /**
     * 获取或创建别名
     *
     * @param selection Selection
     * @return 别名
     */
    private synchronized String getOrCreateAlias(Selection<?> selection) {
        Assert.notNull(selection, "[Assertion failed] - selection is required; it must not be null");

        String alias = selection.getAlias();
        if (alias == null) {
            if (aliasCount > 1000) {
                aliasCount = 0;
            }
            alias = ALIAS_PREFIX + aliasCount++;
            selection.alias(alias);
        }
        return alias;

    }

    /**
     * 拷贝Join
     *
     * @param from 源
     * @param to   目标
     */
    private void copyJoins(From<?, ?> from, From<?, ?> to) {
        Assert.notNull(from, "[Assertion failed] - from is required; it must not be null");
        Assert.notNull(to, "[Assertion failed] - to is required; it must not be null");

        for (Join<?, ?> fromJoin : from.getJoins()) {
            Join<?, ?> toJoin = to.join(fromJoin.getAttribute().getName(), fromJoin.getJoinType());
            toJoin.alias(getOrCreateAlias(fromJoin));
            copyJoins(fromJoin, toJoin);
        }
    }

    /**
     * 拷贝Fetch
     *
     * @param from 源
     * @param to   目标
     */
    private void copyFetches(From<?, ?> from, From<?, ?> to) {
        Assert.notNull(from, "[Assertion failed] - from is required; it must not be null");
        Assert.notNull(to, "[Assertion failed] - to is required; it must not be null");

        for (Fetch<?, ?> fromFetch : from.getFetches()) {
            Fetch<?, ?> toFetch = to.fetch(fromFetch.getAttribute().getName());
            copyFetches(fromFetch, toFetch);
        }
    }

    /**
     * 拷贝Fetch
     *
     * @param from 源
     * @param to   目标
     */
    private void copyFetches(Fetch<?, ?> from, Fetch<?, ?> to) {
        Assert.notNull(from, "from is null");
        Assert.notNull(to, "to is null");

        for (Fetch<?, ?> fromFetch : from.getFetches()) {
            Fetch<?, ?> toFetch = to.fetch(fromFetch.getAttribute().getName());
            copyFetches(fromFetch, toFetch);
        }
    }

    /**
     * 拷贝查询条件(不包含Selection、OrderMember)
     *
     * @param from 源
     * @param to   目标
     */
    public void copyCriteriaWithoutSelectionAndOrder(CriteriaQuery<?> from, CriteriaQuery<?> to) {
        Assert.notNull(from, "[Assertion failed] - from is required; it must not be null");
        Assert.notNull(to, "[Assertion failed] - to is required; it must not be null");

        for (Root<?> root : from.getRoots()) {
            Root<?> dest = to.from(root.getJavaType());
            dest.alias(getOrCreateAlias(root));
            copyJoins(root, dest);
            copyFetches(root, dest);
        }

        to.groupBy(from.getGroupList());
        to.distinct(from.isDistinct());

        if (from.getGroupRestriction() != null) {
            to.having(from.getGroupRestriction());
        }

        if (from.getRestriction() != null) {
            to.where(from.getRestriction());
        }
    }

    /**
     * 转换为Predicate
     *
     * @param root    Root
     * @param filters 筛选
     * @return Predicate
     */
    @SuppressWarnings("unchecked")
    private Predicate toPredicate(Root<T> root, CriteriaQuery<T> criteriaQuery, List<Filter> filters) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        Predicate restrictions = criteriaBuilder.conjunction();
        if (root == null || CollUtil.isEmpty(filters)) {
            return restrictions;
        }
        for (Filter filter : filters) {

            if (filter.getOperator() == null) {
                continue;
            }
            if (!filter.getOperator().equals(or)) {
                if (filter.getValue() == null || filter.getProperty() == null || StringUtils.isBlank(filter.getProperty())) {
                    continue;
                }
                if (filter.getValue() instanceof String) {
                    if (StringUtils.isBlank(filter.getValue().toString())) {
                        continue;
                    }
                }
            }

            String property = filter.getProperty();
            Filter.Operator operator = filter.getOperator();
            Object value = filter.getValue();
            Boolean ignoreCase = filter.getIgnoreCase();
            Path<?> path = getPath(root, property);
            if (path == null) {
                continue;
            }
            switch (operator) {
                case arrayLongsIn:
                    Expression<Long> json_contains = criteriaBuilder.function("json_contains", Long.class, path, criteriaBuilder.literal(value));
                    //jiawei
//                    restrictions = criteriaBuilder.and(restrictions, path.in(value));
//                    criteriaBuilder.and()
                    break;

                case or:
                    List<Filter> filterList = filter.getFilters();
                    if (CollUtil.isNotEmpty(filterList)) {
                        Predicate[] predicates = new Predicate[filterList.size()];
                        for (int i = 0; i < filterList.size(); i++) {
                            List<Filter> filters1 = new ArrayList<>();
                            filters1.add(filterList.get(i));
                            predicates[i] = toPredicate(root, criteriaQuery, filters1);
                        }
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.or(predicates));

                        // restrictions = criteriaBuilder.and(restrictions,
                        //         criteriaBuilder.equal(
                        //                 criteriaBuilder.function(
                        //                         "JSON_EXTRACT", String.class, root.get(""), criteriaBuilder.literal(value)), 1
                        //         )
                        // );
                    }
                    break;
                case subEmpty:
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.isNull(root.join(property, JoinType.LEFT)));
//                        criteria.setProjection(Projections.countDistinct("id"))
                    break;
                case subNotEmpty:
                    restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.isNotNull(root.join(property, JoinType.LEFT)));
                    break;
                case eq:
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.equal(path, value));
                    }
                    break;
                case ne:
                    if (BooleanUtils.isTrue(ignoreCase) && String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                    } else {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.notEqual(path, value));
                    }
                    break;
                case gt:
                    if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.gt((Path<Number>) path, (Number) value));
                    }
                    break;
                case lt:
                    if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lt((Path<Number>) path, (Number) value));
                    }
                    break;
                case ge:
                    if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.ge((Path<Number>) path, (Number) value));
                    }
                    break;
                case le:
                    if (Number.class.isAssignableFrom(path.getJavaType()) && value instanceof Number) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.le((Path<Number>) path, (Number) value));
                    }
                    break;
                case lessThan:
                    if (Date.class.isAssignableFrom(path.getJavaType()) && value instanceof Date) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lessThan((Path<Date>) path, (Date) value));
                    }
                    break;
                case lessThanOrEqualTo:
                    if (Date.class.isAssignableFrom(path.getJavaType()) && value instanceof Date) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.lessThanOrEqualTo((Path<Date>) path, (Date) value));
                    }
                    break;
                case greaterThan:
                    if (Date.class.isAssignableFrom(path.getJavaType()) && value instanceof Date) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.greaterThan((Path<Date>) path, (Date) value));
                    }
                    break;
                case greaterThanOrEqualTo:
                    if (Date.class.isAssignableFrom(path.getJavaType()) && value instanceof Date) {
                        restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.greaterThanOrEqualTo((Path<Date>) path, (Date) value));
                    }
                    break;
                case like:
                    if (String.class.isAssignableFrom(path.getJavaType()) && value instanceof String) {
                        if (BooleanUtils.isTrue(ignoreCase)) {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like(criteriaBuilder.lower((Path<String>) path), ((String) value).toLowerCase()));
                        } else {
                            restrictions = criteriaBuilder.and(restrictions, criteriaBuilder.like((Path<String>) path, (String) value));
                        }
                    }
                    break;
                case in:
                    restrictions = criteriaBuilder.and(restrictions, path.in(value));
                    break;
                case isNull:
                    restrictions = criteriaBuilder.and(restrictions, path.isNull());
                    break;
                case isNotNull:
                    restrictions = criteriaBuilder.and(restrictions, path.isNotNull());
                    break;
                default:
                    break;
            }
        }
        return restrictions;
    }

    /**
     * 转换为Order
     *
     * @param root   Root
     * @param orders 排序
     * @return OrderMember
     */
    private List<Order> toOrders(Root<T> root, List<com.fau.wmbj.server.utils.Order> orders) {
        List<Order> orderList = new ArrayList<>();
        if (root == null || CollUtil.isEmpty(orders)) {
            return orderList;
        }
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        for (com.fau.wmbj.server.utils.Order order : orders) {
            if (order == null) {
                continue;
            }
            String property = order.getProperty();
            com.fau.wmbj.server.utils.Order.Direction direction = order.getDirection();
            Path<?> path = getPath(root, property);
            if (path == null || direction == null) {
                continue;
            }
            switch (direction) {
                case asc:
                    orderList.add(criteriaBuilder.asc(path));
                    break;
                case desc:
                    orderList.add(criteriaBuilder.desc(path));
                    break;
                case str_asc:
                    orderList.add(criteriaBuilder.asc(root.get(property).as(Integer.class)));
                    break;
                case str_desc:
                    orderList.add(criteriaBuilder.desc(root.get(property).as(Integer.class)));
                    break;
                default:
                    break;
            }
        }
        return orderList;
    }

    public int updateBySQL(String sql, String[] p) {
        Query query = entityManager.createQuery(sql);

        int i = query.executeUpdate();
        return i;
    }


}
